package com.hcc.export.helper.core.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import com.alibaba.fastjson.JSON;
import com.hcc.export.helper.core.config.DefaultExportHelperSupport;
import com.hcc.export.helper.core.config.ExportHelperSupport;
import com.hcc.export.helper.core.domain.ExportStatusEnum;
import com.hcc.export.helper.core.domain.ExportTask;
import com.hcc.export.helper.core.domain.po.ExportConfig;
import com.hcc.export.helper.core.domain.po.ExportTaskRecord;
import com.hcc.export.helper.core.event.ExportTaskEvent;
import com.hcc.export.helper.core.exception.ExportException;
import com.hcc.export.helper.core.repository.ExportRepo;
import com.hcc.export.helper.core.service.ExportService;
import com.hcc.export.helper.core.utils.ApplicationContextUtils;
import com.hcc.export.helper.core.utils.CollUtils;
import com.hcc.export.helper.core.utils.ReflectUtils;
import com.hcc.export.helper.core.utils.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service("exportHelperExportService")
public class ExportServiceImpl implements ExportService {

    @Value("${export.helper.exportFileExtendName:xlsx}")
    private String exportFileExtendName;

    private final ExportRepo exportRepo;

    private final ExportHelperSupport exportHelperSupport;

    public ExportServiceImpl(ExportRepo exportRepo, ObjectProvider<ExportHelperSupport> exportHelperSupportObjectProvider) {
        this.exportRepo = exportRepo;
//        this.exportTaskQueue = exportTaskQueue;
        this.exportHelperSupport = exportHelperSupportObjectProvider.getIfAvailable(DefaultExportHelperSupport::new);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addExportConfig(ExportConfig exportConfig) {
        ExportConfig existExportConfig = exportRepo.getExportConfigByExportCode(exportConfig.getExportCode());
        if (existExportConfig != null) {
            throw new ExportException(String.format("导出配置code: [%s]已存在", exportConfig.getExportCode()));
        }
        exportConfig.setMaxRetryCount(Optional.ofNullable(exportConfig.getMaxRetryCount()).orElse(5));

        exportRepo.insertExportConfig(exportConfig);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitExportTask(ExportTaskRecord exportTaskRecord) {
        if (exportTaskRecord == null) {
            return;
        }
        ExportConfig exportConfig = exportRepo.getExportConfigByExportCode(exportTaskRecord.getExportCode());
        Assert.isTrue(exportConfig != null, String.format("导出code: [%s]无导出配置", exportTaskRecord.getExportCode()));

        exportTaskRecord.setExportConfigId(exportConfig.getId());
        exportTaskRecord.setExportStatus(ExportStatusEnum.CREATED.name());
        exportTaskRecord.setRetryCount(0);

        Long id = exportRepo.insertExportTaskRecord(exportTaskRecord);

        // 发送导出事件
        ExportTask exportTask = new ExportTask();
        exportTask.setExportCode(exportConfig.getExportCode());
        exportTask.setExportName(exportConfig.getExportName());
        exportTask.setRecordId(id);
        ApplicationContextUtils.pushEvent(new ExportTaskEvent(exportTask));
    }

    @Override
    public void retryExportTask(Long exportTaskRecordId) {
        if (exportTaskRecordId == null) {
            return;
        }

        ExportTaskRecord exportTaskRecord = exportRepo.getExportTaskRecordById(exportTaskRecordId);
        Assert.isTrue(exportTaskRecord != null, "无导出任务记录");
        Assert.isTrue(!ExportStatusEnum.SUCCESS.name().equals(exportTaskRecord.getExportStatus()), "该导出任务已完成");

        ExportConfig exportConfig = exportRepo.getExportConfigByExportCode(exportTaskRecord.getExportCode());
        Assert.isTrue(exportConfig != null, String.format("导出code: [%s]无导出配置", exportTaskRecord.getExportCode()));

        // 发送导出事件
        ExportTask exportTask = new ExportTask();
        exportTask.setExportCode(exportConfig.getExportCode());
        exportTask.setExportName(exportConfig.getExportName());
        exportTask.setRecordId(exportTaskRecord.getId());
        ApplicationContextUtils.pushEvent(new ExportTaskEvent(exportTask));
    }

    @Override
    public void export(Long exportTaskRecordId) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ExportTaskRecord exportTaskRecord = exportRepo.getExportTaskRecordById(exportTaskRecordId);
        Assert.isTrue(exportTaskRecord != null, "无导出任务记录");

        ExportConfig exportConfig = exportRepo.getExportConfigByExportCode(exportTaskRecord.getExportCode());
        Assert.isTrue(exportConfig != null, String.format("导出code: [%s]无导出配置", exportTaskRecord.getExportCode()));

        ExportTask exportTask = new ExportTask();
        exportTask.setExportCode(exportConfig.getExportCode());
        exportTask.setExportName(exportConfig.getExportName());
        exportTask.setRecordId(exportTaskRecord.getId());

        try {
            // 1. 获取数据
            List<?> results = this.fetchData(exportConfig, exportTaskRecord);
            if (CollUtils.isEmpty(results)) {
                exportTaskRecord.setExportStatus(ExportStatusEnum.ABORT.name());
                exportTaskRecord.setFailedReason("无数据");
                exportRepo.updateExportTaskRecord(exportTaskRecord);
                return;
            }

            // 2. 转换为excel并存放到本地临时目录
            File file = this.write2Excel(exportConfig, exportTaskRecord, results);

            // 3. 上传excel至cos或其它由用户实现的接口中
            String fileUrl = this.upload(file);

            stopWatch.stop();

            // 4. 回写url到任务记录中，并修改任务状态
            exportTaskRecord.setFileUrl(fileUrl);
            exportTaskRecord.setExportStatus(ExportStatusEnum.SUCCESS.name());
            exportTaskRecord.setFailedReason("导出成功");
            exportTaskRecord.setCost(stopWatch.getTotalTimeMillis());

            exportRepo.updateExportTaskRecord(exportTaskRecord);

            // 5. 导出成功后执行
            this.afterExportSuccess(exportTask);
        } catch (Exception e) {
            exportTaskRecord.setExportStatus(ExportStatusEnum.FAILED.name());
            exportTaskRecord.setFailedReason("导出异常：" + e.getMessage());
            exportRepo.updateExportTaskRecord(exportTaskRecord);

            this.afterExportFailed(exportTask, e);

            log.error(String.format("导出任务id: [%s]导出失败", exportTaskRecord.getId()), e);
        }
    }

    /**
     * 获取数据
     * @param exportConfig
     * @param exportTaskRecord
     * @return
     */
    private List<?> fetchData(ExportConfig exportConfig, ExportTaskRecord exportTaskRecord) {
        Class<?> beanClass = ReflectUtils.forName(exportConfig.getExportBeanClassName());

        // 获取导出的bean
        Object exportBean;
        if (StrUtils.isEmpty(exportConfig.getExportBeanName())) {
            exportBean = ApplicationContextUtils.getBean(beanClass);
        } else {
            exportBean = ApplicationContextUtils.getBean(exportConfig.getExportBeanName(), beanClass);
        }

        // 获取导出的方法
        Method method;
        Object methodParam;
        String exportParamClassName = exportConfig.getExportParamClassName();
        if (StrUtils.isEmpty(exportParamClassName)) {
            method = ReflectUtils.getDeclaredMethod(beanClass, exportConfig.getExportMethodName(), String.class);
            methodParam = exportTaskRecord.getExportParamJson();
        } else {
            Class<?> paramClass = ReflectUtils.forName(exportParamClassName);
            method = ReflectUtils.getDeclaredMethod(beanClass, exportConfig.getExportMethodName(), paramClass);
            methodParam = JSON.parseObject(exportTaskRecord.getExportParamJson(), paramClass);
        }
        return (List<?>) ReflectUtils.invokeMethod(exportBean, method, methodParam);
    }

    /**
     * 写入excel
     * @param exportTaskRecord
     * @param data
     * @return
     */
    private File write2Excel(ExportConfig exportConfig, ExportTaskRecord exportTaskRecord, List<?> data) {
        String fileName = exportTaskRecord.getFileName() + "." + exportFileExtendName;
        String filePath = "/temp/hcc-export-helper"
                + File.separator
                + exportConfig.getExportCode();

        try {
            Files.createDirectories(Paths.get(filePath));
        } catch (IOException e) {
            throw new ExportException(String.format("创建目录：%s失败", filePath));
        }

        // 创建文件
        File file = new File(filePath, fileName);
        int seq = 0;
        while (true) {
            if (!file.exists()) {
                try {
                    boolean success = file.createNewFile();
                    if (success) {
                        break;
                    }
                } catch (IOException e) {
                    throw new ExportException(String.format("创建文件：%s失败", fileName));
                }
            } else {
                String newFileName = exportTaskRecord.getFileName() + (++seq) + "." + exportFileExtendName;
                file = new File(filePath, newFileName);
            }
        }

        ExportParams exportParams = new ExportParams();

        Class<?> resultClass = ReflectUtils.forName(exportConfig.getExportResultClassName());
        try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, resultClass, data);
             OutputStream outputStream = Files.newOutputStream(file.toPath())) {
            workbook.write(outputStream);
        } catch (Exception e){
            throw new ExportException("写入excel失败");
        }

        return file;
    }

    /**
     * 上传
     * @param file
     * @return
     */
    private String upload(File file) {
        try {
            return exportHelperSupport.upload(file);
        } finally {
            if (file.exists()) {
                file.delete();
            }
        }
    }

    private void afterExportSuccess(ExportTask exportTask) {
        try {
            exportHelperSupport.afterExportSuccess(exportTask);
        } catch (Exception e) {
            log.error("执行导出成功回调方法异常", e);
        }
    }

    private void afterExportFailed(ExportTask exportTask, Exception e) {
        try {
            exportHelperSupport.afterExportFailed(exportTask, e);
        } catch (Exception ex) {
            log.error("执行导出成失败回调方法异常", e);
        }
    }

    @Override
    public List<ExportTaskRecord> listFailedRecord(String exportCode) {
        return null;
    }

}
